/*
 * PICO for MOTO EZX Modile Phone
 * Copyright (C) 2006 OopsWare. CHINA.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: smd_player.cpp,v 0.10 2006/07/06 $
 */

#include "smd_player.h"
#include <qapplication.h>
#include "ezxaudio.h"

#include "PicoInt.h"

#define AUDIO_BITRATE	11025
#define AUDIO_CHANNELS	2
#define AUDIO_FREGMENT	(11 | 4 << 16)
#define AUDIO_FORMAT	AFMT_S16_LE

static unsigned char * RomData 	= NULL;
static unsigned int RomSize 	= 0;
static int dspfd = -1;
static int frame_count = 0;
/*
//#define AUDIO_LEN		735
//#define AUDIO_LEN		(AUDIO_BITRATE / 50)
*/

#define PICO_UP_MASK	(1)
#define PICO_DOWN_MASK	(1 << 1)
#define PICO_LEFT_MASK	(1 << 2)
#define PICO_RIGHT_MASK	(1 << 3)
#define PICO_B_MASK		(1 << 4)
#define PICO_C_MASK	    (1 << 5)
#define PICO_A_MASK	    (1 << 6)
#define PICO_START_MASK (1 << 7)

#define PICO_Z_MASK		(1 << 8)
#define PICO_Y_MASK	    (1 << 9)
#define PICO_X_MASK	    (1 << 10)
#define PICO_MODE_MASK	(1 << 11)

static unsigned short framebuffer[8+240+8][320+8];
extern "C" unsigned short * framebuff = &framebuffer[0][0];

struct keymap_item SMD_KEYMAP[] = {
                {"EXIT",        0, false,  0},
                {"UP",          1, false,  PICO_UP_MASK},
		{"DOWN",	2, false,  PICO_DOWN_MASK},
		{"LEFT",	3, false,  PICO_LEFT_MASK},
		{"RIGHT", 	4, false,  PICO_RIGHT_MASK},

		{"START",	5, false,  PICO_START_MASK},
		{"MODE",	6, false,  PICO_MODE_MASK},
		{"A",		7, false,  PICO_A_MASK},
		{"B", 		8, false,  PICO_B_MASK },
		{"C",		9, false,  PICO_C_MASK },
		{"X",		10, false,  PICO_X_MASK },
		{"Y",		11, false,  PICO_Y_MASK },
		{"Z",		12, false,  PICO_Z_MASK },
		{"A+B",		13, false,  PICO_A_MASK + PICO_B_MASK},
		{"A+C",		14, false,  PICO_A_MASK + PICO_C_MASK},
		{"B+C",		15, false,  PICO_B_MASK + PICO_C_MASK},
		{"A+B+C",	16, false,  PICO_A_MASK + PICO_B_MASK + PICO_C_MASK},
		
		{"A-Turbo",	7, true,  PICO_A_MASK},
		{"B-Turbo",	8, true,  PICO_B_MASK},
		{"C-Turbo",	9, true,  PICO_C_MASK}	};

static void do_keypad()
{
    
	static unsigned int turbo = 0;
	turbo ++;

	unsigned int tmp = 0;

//	if ( HKCS[EZX_KEY_C].down )		tmp |= PICO_START_MASK;
	int func_index;
	
	for (int i=1;i<20;i++) {
		func_index = GameKeymap[i];
		if ( func_index != -1 && HKCS[func_index].down) {
			tmp |= SMD_KEYMAP[i].mask;
                        //TODO TURBO KEYS
//			if (HKCS[i].down && SMD_KEYMAP[func_index].turbo)
//				function_keys[ SMD_KEYMAP[func_index].keypad ] = turbo & 0x2;
		}
	}
	
	PicoPad[0] = tmp;
}

static int EmulateScan(unsigned int scan, unsigned short *sdata)
{
	if (scan >= 224) return 0;
	memcpy( & (VideoBuffer[scan + 8]), sdata, 640 );
	return 0;
}

static int EmuScan16(unsigned int num, void *sdata)
{
    
	if (!(Pico.video.reg[1]&8)) {
            num += 8;
        }
//	DrawLineDest = (unsigned short *) gp2x_screen + 320*(num+1);
	DrawLineDest = (unsigned short *) & (VideoBuffer[num+1][0]);

	return 0;
}

typedef unsigned int (*STATE_SL_FUNC)(void *, unsigned int, unsigned int, void *);

bool smd_FileState(bool load, const char *fn)
{
	FILE * PmovFile;
	if ( load )	PmovFile = fopen (fn, "rb");
	else		PmovFile = fopen (fn, "wb");

	if ( !PmovFile ) return false;
	
	areaRead  = (STATE_SL_FUNC) fread;
	areaWrite = (STATE_SL_FUNC) fwrite;
	PmovState(load ? 6 : 5, PmovFile);
		
	fclose(PmovFile);
	Pico.m.dirtyPal = 1;

	return true;
}

QString SRamSaveName("");

bool smd_SRamState(bool load, const char *fn)
{
	FILE * PmovFile;
	int sram_size = SRam.end - SRam.start + 1;
	if(SRam.reg_back & 4) sram_size=0x2000;
	
	if (!load) {
		// Do not save '.srm' while SRam size is zero or SRam is empty 
		for(; sram_size > 0; sram_size--)
			if(SRam.data[sram_size-1]) break;
		if (!sram_size)
			return true;
	}
	
	PmovFile = fopen (fn, load ? "rb" : "wb");
	if ( !PmovFile ) return false;
	
	if (!load) {
		if ( sram_size ) {
			int res = fwrite(SRam.data, 1, sram_size, PmovFile);
			fclose(PmovFile);
			PmovFile = 0;
			return (res != sram_size) ? false : true;
		}
		return true;
	}
	
	fread(SRam.data, 1, sram_size, PmovFile);
	fclose(PmovFile);
	PmovFile = 0;

	return true;
}

//extern int PicoMask;

extern "C" void updateSound(void)
{
	//gp2x_sound_write(PsndOut, (PicoOpt&8)?PsndLen*4:PsndLen*2);
	if (dspfd >= 0)	// && (PsndLen > 0))
		ezx_play_dsp ( dspfd, &EzxAudioBuffer, PsndLen << 2 );
}

void run_smd_emulator(const char *fn, bool iszip)
{
	int fk, res;
	int modes, oldmodes = 0;

//        printf ( "%d" , sizeof(int));
	printf("\nInit PicoDrive v%04x for E680i\n", PicoVer);

	wgGame = new SMD_GameWidget( 0 );
	wgGame->doLoadConfig();
	
//        wgMain->getVBoxLayout()->add(wgGame);

	frame_count = 0;
        
        wgGame->setFrameBufferDepth(16);
        
	wgGame->showFullScreen();

	wgGame->UpdateRect(0, 0, 320, 240, true);
//return ;
	qApp->processEvents();

#if 1
	PsndRate = AUDIO_BITRATE;
	//PsndLen  = AUDIO_BITRATE / AUDIO_LEN;
	PsndOut	 = NULL;

	if ( GameMute )	dspfd = -1;	else {
		dspfd = ezx_open_dsp ( AUDIO_BITRATE, AUDIO_CHANNELS, AUDIO_FORMAT, AUDIO_FREGMENT );
		printf("SOUND: Init done (%d)\n", dspfd);
		if (dspfd >= 0) {
			PsndOut	 = (short *) &EzxAudioBuffer;
			PicoWriteSound = updateSound;
		}
	}
#endif

	PicoOpt = 0x2F; // 6 button gamepad enabled
	switch ( GameScreenMode ) {
	 case 1: PicoRegionOverride = 1; break; // Japan-NTSC
 	 case 2: PicoRegionOverride = 2; break; // Japan-PAL
	 case 3: PicoRegionOverride = 4; break; // US
	 case 4: PicoRegionOverride = 8; break; // Europe
	default: PicoRegionOverride = 0; break; // Auto
	};

	PicoInit();
	printf("Load ROM %s\n", fn);

	if ( iszip ) {
		// load zip format
		res = CartLoadZip(fn, &RomData, &RomSize);
		if ( res ) {

			goto finish;
		}
	} else {
		// load .smd format
		FILE * f = fopen( fn, "rb" );
		if (!f) goto finish;

		res = PicoCartLoad(f, &RomData, &RomSize);
		if ( res ) {
			fclose(f);
			goto finish;
		}
	}
	if(RomSize <= 0x200 || strncmp((char *)RomData, "Pico", 4) == 0 ||
	  ((*(unsigned short *)(RomData+4)<<16)|(*(unsigned short *)(RomData+6))) >= (int)RomSize) {
		if (RomData) free(RomData);
		RomData = 0;
		printf("Not a ROM selected.\n");
		goto finish;
	}

	PicoCartInsert(RomData, RomSize);


	printf("ROM Size: %d Bytes Region: ", RomSize);
	switch(Pico.m.hardware)
	{
		case 0xe0:
			printf("Europe\n");
			break;
		case 0xa0:
			printf("USA\n");
			break;
		case 0x60:
			printf("Japan PAL\n");
			break;
		case 0x20:
			printf("Japan NTSC\n");
			break;
		default:
			printf("Unknown\n");
			break;
	}
	PicoPad[0] = 0;
	PicoPad[1] = 0;

	// vidResetMode(1);
	DrawSetColorFormat(1);
	PicoScan = EmuScan16;
	PicoScan(0, 0);
	Pico.m.dirtyPal = 1;
	oldmodes = ((Pico.video.reg[12]&1)<<2) ^ 0xc;

	printf("Check SRam ...\n");

	SRamSaveName = wgGame->RomSaveName;
	SRamSaveName += ".srm";
	smd_SRamState( true, SRamSaveName.local8Bit() );

	printf("Emulatting ... \n");

	GameLooping = true;
	while (GameLooping) {
		qApp->processEvents();
		if ( GamePauseing ) {
			wgGame->PauseProc();
			continue;
		}
		do_keypad();

		// check for mode changes
		modes = ((Pico.video.reg[12]&1)<<2)|(Pico.video.reg[1]&8);
		if (modes != oldmodes) {
			// do something while screen mode change
			oldmodes = modes;
			// clearArea(1);
		}


		fk = GameFrameSkip;
		if (fk < 0) fk = 1;
		PicoSkipFrame = (frame_count) ? 1 : 0;

		PicoFrame();
		PicoSkipFrame = 0;

		if (!frame_count)
			wgGame->UpdateRect(0, 8, 320, 224);


#ifdef ARM9_SOUND
//		printf( "PsndLen: %d\n" , PsndLen);
		if ((dspfd >= 0) && (PsndLen > 0))
			ezx_play_dsp ( dspfd, &EzxAudioBuffer, PsndLen << 2 );
#endif

		if (frame_count >= fk)
			frame_count = 0;
		else
			frame_count ++;
	}

	if ( SRam.changed ) {
		smd_SRamState( false, SRamSaveName.local8Bit() );
		SRam.changed = 0;
	}

finish:

	//SRamState(0, 0);

	PsndRate = 0;
	PsndOut	= NULL;
	PsndLen = 0;

	printf("PicoDrive shutdown .\n");
	PicoExit();
	if ( RomData )
		free( RomData );
	RomData = NULL;
	RomSize = 0;
        
	if (dspfd >= 0)
		ezx_close_dsp(dspfd);
	dspfd = -1;
	printf("\n");
        
        wgGame->setFrameBufferDepth(24);
//        wgGame->erase();
//        wgGame->repaint();
//        wgGame->hide();
//        wgMain->getVBoxLayout()->removeChild(wgGame);
//        wgGame->cleanFrameBuffer();
//        qApp->quit();
//        qApp->processEvents();
//        wgGame->showNormal();
//	if (wgGame) {
//		delete wgGame;
//		wgGame = 0;
//	}
        
}

void SMD_GameWidget::doGamePause(bool pause)
{
//qWarning("pause: %d\n", pause);
	if ( pause ) {
		
		if (dspfd >= 0)
			ezx_close_dsp(dspfd);
  		dspfd = -1;

	} else {

//printf(" pause: %d, GameLoop: %d \n", (int)pause, (int)GameLooping);

		frame_count = 0;
		if (GameMute) PsndOut = NULL;
#if 1
		if ((dspfd < 0) && !GameMute) {
			dspfd = ezx_open_dsp ( AUDIO_BITRATE, AUDIO_CHANNELS, AUDIO_FORMAT, AUDIO_FREGMENT );
			if (dspfd >= 0)
				PsndOut	 = (short *) (&EzxAudioBuffer);
  		}
#endif

	}
}

bool SMD_GameWidget::doGameReset()
{
	// hard reset
	return (PicoReset(0) == 0);
}

bool SMD_GameWidget::doSaveGame(const char * fn)
{
	if (smd_FileState(false, fn))
		if (smd_SRamState(false, SRamSaveName.local8Bit() ))
			return true;
	return false;
}

bool SMD_GameWidget::doLoadGame(const char * fn)
{
	if (smd_FileState(true, fn)) {
		smd_SRamState(true, SRamSaveName.local8Bit() );
		return true;
	}
	return false;
}

QString SMD_GameWidget::doConfigItemPerfix(const QString &s) const
{
	return "SMD_" + s;
}

int SMD_GameWidget::getKeymapCount()
{
	return sizeof(SMD_KEYMAP) / sizeof(struct keymap_item);
}

struct keymap_item * SMD_GameWidget::getKeymapData()
{
	return &SMD_KEYMAP[0];
}
